package intopo.game.impl.sym2x2;

import intopo.evogame.partnerselection.Cache;
import intopo.evogame.partnerselection.config.AbstractTextConfigFile;
import intopo.evogame.partnerselection.config.ParsingException;
import intopo.game.Strategy;

import java.io.PrintStream;
import util.xml.*;

import intopo.util.Util;
import java.util.Scanner;
import org.w3c.dom.Element;


/**
 * A class that represents a strategy in a 2-player 2x2 symmetric
 * game. Since there are only two actions available and the game is
 * symmetric, this strategy only requires one parameter that specifies the
 * probability of playing the first action. Interpretation of these actions
 * depends on the instance of class {@code Symmetric2x2Game}.
 *
 * @author <a href="mailto:plsm@ua.pt">Pedro Lopes da Silva Mariano</a>
 *
 * @version 1.0
 */
final public class Symmetric2x2Strategy
	implements Strategy<Symmetric2x2Strategy>
{
	/**
	 *
	 */
	static String CONSTRUCTOR_TAG = "Symmetric2x2Strategy";
	/**
	 * Probability to play the first action.
	 */
	final float firstActionProbability;
	
	/**
	 * Constructs a new instance of a strategy to play a 2-player 2x2
	 * symmetric game.
	 */
	public Symmetric2x2Strategy (float firstActionProbability)
	{
		assert firstActionProbability >= 0 && firstActionProbability <= 1.0;

		this.firstActionProbability = firstActionProbability;
	}
	public Symmetric2x2Strategy (Scanner scanner)
	{
		this (scanner.nextFloat ());
	}
	/**
	 *
	 * @param element
	 * @throws util.xml.XMLParseException
	 */
	public Symmetric2x2Strategy (Element element)
		throws XMLParseException
	{
		AttributeParser<Double> apProb = new AttributeParser<Double>
			(new CheckExistenceAttribute<Double> (),
			 new ParseAsDouble (0, 1));
		firstActionProbability = apProb.parse (element, "Probability", "value").floatValue ();
	}

	@Override
	public Symmetric2x2Strategy clone ()
	{
		return new Symmetric2x2Strategy (firstActionProbability);
	}

	public int numberParameters ()
	{
		return 1;
	}

	public Object getParameterValue (int index)
	{
		return new Float (firstActionProbability);
	}

	public boolean firstAction ()
	{
		return Cache.supply.nextFloat () <= firstActionProbability;
	}

	@Override
	public int getBin ()
	{
		int r = (int) Math.floor (firstActionProbability * Parameters.probabilityBins);
		if (r == Parameters.probabilityBins) {
			return r - 1;
		}
		else {
			return r;
		}
	}
	static public String getBinDescription (int bin)
	{
		float p;
		p = bin / (float) Parameters.probabilityBins;
		return Float.toString (p);
	}
	
	@Override
	public boolean isCooperator ()
	{
		return this.firstActionProbability >= 0.9;
	}

	/**
	 * Return a mutated copy of this strategy.  A normal distribution with mean 0 and standard deviation {@code Parameters.normalStandardDeviation} is added to this strategy probability to form the returned strategy probability.
	 * @return a mutated copy of this strategy.
	 * @see Parameters#normalStandardDeviation
	 */ 
	@Override
	public Symmetric2x2Strategy getMutantAllGenes ()
	{
		float p = Util.perturbNormal (this.firstActionProbability, Parameters.normalStandardDeviation, 0f, 1f);
		return new Symmetric2x2Strategy (p);
	}

	@Override
	public Symmetric2x2Strategy getMutantSingleGene ()
	{
		float p = Util.perturbNormal (this.firstActionProbability, Parameters.normalStandardDeviation, 0f, 1f);
		return new Symmetric2x2Strategy (p);
	}

	@Override
	public void print (java.io.PrintStream ps)
	{
		ps.print (Symmetric2x2Strategy.CONSTRUCTOR_TAG);
		ps.print (' ');
		ps.print (firstActionProbability);
	}
		/**
	 * Writes this strategy to the given binary stream.
	 * <p> The data format is:
	 * <pre>
	 * | byte 1 | byte 2 | byte 3 | byte 4 |
	 * |      first action probability     |
	 * |                                   |
	 * </pre>
	 * @param dos The binary stream where the strategy is written.
	 * @throws java.io.IOException If an I/O error occurs.
	 */
	@Override
	public void write (java.io.DataOutputStream dos)
		throws java.io.IOException
	{
		dos.writeFloat (this.firstActionProbability);
	}
	@Override
	public String toString ()
	{
		return "Strategy to a symmetric 2-player game (" + firstActionProbability + ")";
	}

	/**
	 * Contains Provides static fields used in calculating the <i>bin</i> of the enclosing strategy.
	 */
	static public class Parameters
	{
		/**
		 * How many bins has the strategy probability. Used in calculating the <i>bin</i> of the enclosing strategy.
		 * @see Symmetric2x2StrategyPlayer#getBin()
		 */
		static int probabilityBins = 3;
	
		/**
		 * Standard deviation of the normal distribution used to mutate the strategy probability.
		 * @see Symmetric2x2StrategyPlayer#getMutated(RandomEngine)
		 * @see Symmetric2x2StrategyPlayer#firstActionProbability
		 */
		static float normalStandardDeviation = 0.1f;
		/**
		 *
		 * @param scanner
		 */
		static public void read (Scanner scanner) throws ParsingException
		{
			Parameters.probabilityBins = AbstractTextConfigFile.readInt (scanner, "Number of bins");
			Parameters.normalStandardDeviation = AbstractTextConfigFile.readFloat (scanner, "Standard deviation of the normal distribution used to mutate the probability to select the first action");
		}
		/**
		 * 
		 * @param ps
		 */
		static void write (PrintStream ps)
		{
			ps.print (Parameters.probabilityBins);
			ps.println ("\t# Number of bins");
			ps.print (Parameters.normalStandardDeviation);
			ps.println ("\t# Standard deviation of the normal distribution used to mutate the probability to select the first action");
		}
	}
}
